package orm

import (
	"animal/octopus/common"
	"animal/octopus/model"

	log "github.com/sirupsen/logrus"
	"gorm.io/gorm"
)

// ------------------------------------------------------------------------------
//
// ------------------------------------------------------------------------------
func GetTopoViews(db *gorm.DB, moduleID uint) ([]model.TopoView, error) {
	var topoViews []model.TopoView
	result := common.DB(db).Find(&topoViews, "module_id = ?", moduleID)
	if result.Error != nil {
		log.WithFields(log.Fields{
			"module": moduleID,
			"error":  result.Error,
		}).Error("find topo views failed")
		return nil, result.Error
	}

	return topoViews, nil
}

// ------------------------------------------------------------------------------
//
// ------------------------------------------------------------------------------
func GetDefaultTopoView(db *gorm.DB, moduleID uint) (*model.TopoView, error) {
	module, ok := common.FindItemByID[model.Module](db, moduleID)
	if !ok {
		log.WithField("ID", moduleID).Error("cannot find module")
		return nil, common.NewErrCode(common.ErrGeneralError)
	}

	var topoView model.TopoView
	topoViewName := module.Name + "-topo-view"

	if err := common.DB(db).Where("module_id = ?", moduleID).
		Where("name = ?", topoViewName).First(&topoView).Error; err != nil {
		log.WithFields(log.Fields{"moduleId": moduleID, "name": topoViewName}).
			Error("cannot find topo view")
		return nil, err
	}

	return &topoView, nil
}

/////////////////////////////////////////////////////////////////////////////////

// ------------------------------------------------------------------------------
//
// ------------------------------------------------------------------------------
func GetTopoViewList(db *gorm.DB, moduleID uint) ([]common.TopoViewInfo, error) {
	log.WithField("module", moduleID).Info("start getting topo view list")

	// a module may has more than one topo view
	var topoViewInfos []common.TopoViewInfo

	if err := common.DB(db).Transaction(func(tx *gorm.DB) error {
		// find all topo views of module
		var topoViews []model.TopoView
		result := tx.Model(&model.TopoView{}).Preload("Modules").Preload("Rules").
			Find(&topoViews, "module_id = ?", moduleID)
		if result.Error != nil {
			log.Error("database error")
			return common.NewErrCode(common.ErrDatabaseError)
		}

		for _, view := range topoViews {
			topoViewInfo := common.TopoViewInfo{
				ID:         view.ID,
				Name:       view.Name,
				Desc:       view.Desc,
				ModuleID:   view.ModuleID,
				CreateTime: common.TimeFormat(view.CreatedAt),
				ModuleSize: uint(len(view.Modules)),
				RuleSize:   uint(len(view.Rules)),
			}
			topoViewInfos = append(topoViewInfos, topoViewInfo)
		}

		return nil
	}); err != nil {
		log.WithFields(log.Fields{"module": moduleID, "error": err}).
			Error("get topo view list failed")
		return nil, err
	}

	log.WithField("size", len(topoViewInfos)).Info("get topo view list success")

	return topoViewInfos, nil
}

// ------------------------------------------------------------------------------
//
// ------------------------------------------------------------------------------
func CreateTopoView(db *gorm.DB, param common.CreateTopoViewParam) (model.TopoView, error) {
	topoView := model.TopoView{
		Name:     param.Name,
		Desc:     param.Desc,
		ModuleID: param.ModuleID,
	}

	if err := common.DB(db).Transaction(func(tx *gorm.DB) error {
		// check topo view existence
		if _, ok := common.FindItemByName[model.TopoView](tx, param.Name); ok {
			log.WithField("module", param.Name).Error("topo view already exists")
			return common.NewErrCode(common.ErrGeneralError)
		}

		if result := tx.Create(&topoView); result.Error != nil {
			log.WithField("name", param.Name).Error("create topo view failed")
			return common.NewErrCode(common.ErrDatabaseError)
		}

		return nil
	}); err != nil {
		log.WithField("param", param).Error("create topo view failed")
		return model.TopoView{}, err
	}

	log.WithField("param", common.MustMarshal(param)).Info("create topo view success")

	return topoView, nil
}

// ------------------------------------------------------------------------------
//
// ------------------------------------------------------------------------------
func EditTopoView(db *gorm.DB, param common.EditTopoViewParam) error {
	if err := common.DB(db).Transaction(func(tx *gorm.DB) error {
		// check topo view existence
		topoView, ok := common.FindItemByID[model.TopoView](tx, param.ID)
		if !ok {
			log.WithField("view", param.ID).Error("cannot find topo view")
			return common.NewErrCode(common.ErrGeneralError)
		}

		// check name conflict
		v, ok := common.FindItemByName[model.TopoView](tx, param.Name)
		if ok && v.ID != param.ID {
			log.Error("topo view name conflict")
			return common.NewErrCode(common.ErrGeneralError)
		}

		topoView.Name = param.Name
		topoView.Desc = param.Desc
		if err := tx.Save(&topoView).Error; err != nil {
			log.WithFields(log.Fields{
				"view":  topoView,
				"error": err,
			}).Error("update topo view failed")
			return common.NewErrCode(common.ErrGeneralError)
		}

		return nil
	}); err != nil {
		log.WithField("view", param.ID).Error("edit topo view failed")
		return err
	}

	log.WithField("view", param.ID).Info("edit topo view success")

	return nil
}

// ------------------------------------------------------------------------------
//
// ------------------------------------------------------------------------------
func DeleteTopoView(db *gorm.DB, param common.DeleteTopoViewParam) error {
	if err := common.DB(db).Where("id = ?", param.ID).Unscoped().
		Delete(&model.TopoView{}).Error; err != nil {
		log.WithFields(log.Fields{
			"view":  param.ID,
			"error": err,
		}).Error("delete topo view failed")
		return common.NewErrCode(common.ErrDatabaseError)
	}
	return nil
}
